package co.edu.udistrital;

import java.awt.BorderLayout;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Random;

import javax.swing.JFrame;

/**
 * Esta clase tiene la logica del juego
 *
 */
public class Tetris extends JFrame {
	
	/**
	 * The Serial Version UID.
	 */
	private static final long serialVersionUID = 1L;

	/**
	 * Numero de milisegundos por cuadro
	 */
	private static final long TIEMPO_CUADRO = 1000L / 50L;
	
	/**
	 * Numero de figuras
	 */
	private static final int TOTAL_FIGURAS = Figura.values().length;
		
	/**
	 * Instancia del tablero
	 */
	private Tablero tablero;
	
	/**
	 * Instancia del panel general.
	 */
	private PanelGeneral panelGeneral;
	
	/**
	 * Indica si el juego se encuentra pausado o no.
	 */
	private boolean estaPausado;
	
	/**
	 * Indica si es la primera vez que se juega
	 */
	private boolean esJuegoNuevo;
	
	/**
	 * Indica si el juego se perdio o no
	 */
	private boolean esJuegoPerdido;
	
	/**
	 * Nivel actual
	 */
	private int nivel;
	
	/**
	 * Puntuacion actual.
	 */
	private int puntos;
	
	/**
	 * Generador de numeros aleatorios
	 */
	private Random random;
	
	/**
	 * Instancia de la clase que mantiene la logica del tiempo.
	 */
	private Reloj relojLogico;
				
	/**
	 * Figura actual.
	 */
	private Figura figuraActual;
	
	/**
	 * Siguiente figura.
	 */
	private Figura siguienteFigura;
		
	/**
	 * Columna actual del cuadro.
	 */
	private int columnaActual;
	
	/**
	 * Fila actual del cuadro.
	 */
	private int filaActual;
	
	/**
	 * Rotacion actual del de la figura.
	 */
	private int rotacionActual;
		
	/**
	 * Se asegura de que haya pasado cierta cantidad de tiempo.
	 */
	private int tiempoTranscurrido;
	
	/**
	 * Velocidad del juego
	 */
	private float velocidadJuego;
	
	/**
	 * Posicion inicial en x en la que se da click al mouse
	 */
	private int posicionInicialMouse;
	
	/**
	 * Posicion final en x en la que se solto el mouse
	 */
	private int posicionFinalMouse;
	
	/**
	 * Verifica si se trata del boton para mover o si es el boton para rotar
	 */
	private boolean esMover = false;
		
	/**
	 * Constructor de la clase.
	 */
	private Tetris() {

		super("Tetris");
		setLayout(new BorderLayout());
		setDefaultCloseOperation(EXIT_ON_CLOSE);
		setResizable(false);
		
		/*
		 * Inicializa el tablero y el panel general
		 */
		this.tablero = new Tablero(this);
		this.panelGeneral = new PanelGeneral(this);
		
		/*
		 * Añade el borde del panel y el panel general a la ventana
		 */
		add(tablero, BorderLayout.CENTER);
		add(panelGeneral, BorderLayout.EAST);
		
		/*
		 * Acciones de escucha del mouse
		 */
		addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
            	if(!estaPausado) {
					
            		if (e.getButton()== 1){
            			posicionInicialMouse = e.getX();
            			esMover = true;
            		}else{
            			rotarFigura((rotacionActual == 0) ? 3 : rotacionActual - 1);
            			esMover= false;
            		}
					
				}
            }

            @Override
            public void mouseReleased(MouseEvent e) {
            	posicionFinalMouse = e.getX();
            		posicionFinalMouse = (posicionFinalMouse - posicionInicialMouse)/24;
            	
            	if(esMover && !estaPausado && tablero.esPosicionValida(figuraActual, columnaActual +posicionFinalMouse, filaActual, rotacionActual)) {
					columnaActual=columnaActual+posicionFinalMouse;
					esMover = false;
				}
            }
            
        });
		
		/*
		 * Acciones de escucha del teclado.
		 */
		addKeyListener(new KeyAdapter() {
			
			@Override
			public void keyPressed(KeyEvent e) {
								
				switch(e.getKeyCode()) {
				
				case KeyEvent.VK_S:
					if(!estaPausado && tiempoTranscurrido == 0) {
						relojLogico.setCilosPorSegundo(25.0f);
					}
					break;
					
				case KeyEvent.VK_A:
					if(!estaPausado && tablero.esPosicionValida(figuraActual, columnaActual - 1, filaActual, rotacionActual)) {
						columnaActual--;
					}
					break;
					
				case KeyEvent.VK_D:
					if(!estaPausado && tablero.esPosicionValida(figuraActual, columnaActual + 1, filaActual, rotacionActual)) {
						columnaActual++;
					}
					break;
					
				case KeyEvent.VK_Q:
					if(!estaPausado) {
						rotarFigura((rotacionActual == 0) ? 3 : rotacionActual - 1);
					}
					break;
				
				case KeyEvent.VK_E:
					if(!estaPausado) {
						rotarFigura((rotacionActual == 3) ? 0 : rotacionActual + 1);
					}
					break;
					
				case KeyEvent.VK_P:
					if(!esJuegoPerdido && !esJuegoNuevo) {
						estaPausado = !estaPausado;
						relojLogico.setPausado(estaPausado);
					}
					break;
				
				case KeyEvent.VK_ENTER:
					if(esJuegoPerdido || esJuegoNuevo) {
						reiniciarJuego();
					}
					break;
				
				}
			}
			
			@Override
			public void keyReleased(KeyEvent e) {
				
				switch(e.getKeyCode()) {
				
				case KeyEvent.VK_S:
					relojLogico.setCilosPorSegundo(velocidadJuego);
					relojLogico.reiniciarReloj();
					break;
				}
				
			}
			
		});
		
		pack();
		setLocationRelativeTo(null);
		setVisible(true);
	}
	
	/**
	 * Comienza el juego, el while es quien se encarga de estar llenando el tablero
	 * y mostrando el momento del juego.
	 */
	private void comenzarJuego() {
		
		this.random = new Random();
		this.esJuegoNuevo = true;
		this.velocidadJuego = 1.0f;
		
		this.relojLogico = new Reloj(velocidadJuego);
		relojLogico.setPausado(true);
		
		while(true) {
			//Se toma el timepo inicial.
			long start = System.nanoTime();
			
			//Se actualiza el reloj.
			relojLogico.actualizarReloj();
			
			/*
			 * Si ha transucrrido un ciclo se actualiza el tablero y el juego.
			 */
			if(relojLogico.HaTranscurridoCiclo()) {
				actualizarJuego();
			}
		
			//Se decrementa el tiempo transcurrido si es necesario.
			if(tiempoTranscurrido > 0) {
				tiempoTranscurrido--;
			}
			
			//Re despliega la parte visual para el usuario
			renderGame();
			
			long diferencia = (System.nanoTime() - start) / 1000000L;
			if(diferencia < TIEMPO_CUADRO) {
				try {
					Thread.sleep(TIEMPO_CUADRO - diferencia);
				} catch(Exception e) {
					e.printStackTrace();
				}
			}
		}
	}
	
	/**
	 * Actualiza el tablero
	 */
	private void actualizarJuego() {
		/*
		 * Verifica si la figura puede mover hacia abajo
		 */
		if(tablero.esPosicionValida(figuraActual, columnaActual, filaActual + 1, rotacionActual)) {
			filaActual++;
		} else {
			/*
			 * Se ha chocado contra la parte inferior o contra el tablero, debe colocarse la figura
			 */
			tablero.agregarFigura(figuraActual, columnaActual, filaActual, rotacionActual);
			
			/*
			 * Se verifica si añadiendo la nueva figura se limpian filas, si es asi
			 * se agregan puntos, cada cuatro lineas es un grupo: 
			 * [1 = 100pts, 2 = 200pts, 3 = 400pts, 4 = 800pts]).
			 */
			int limpio = tablero.verificarLineas();
			if(limpio > 0) {
				puntos += 50 << limpio;
			}
			
			/*
			 * Aumentar la velocidad un poquito para la siguiente pieza, y actualizar 
			 * el temporizador del juego para reflejar el aumento.
			 */
			velocidadJuego += 0.035f;
			relojLogico.setCilosPorSegundo(velocidadJuego);
			relojLogico.reiniciarReloj();
			
			/*
			 * Se da una espera para que el usuario reaccione a la siguiente pieza
			 */
			tiempoTranscurrido = 25;
			
			/*
			 * Actualizar el nivel de dificultad.
			 */
			nivel = (int)(velocidadJuego * 1.70f);
			
			/*
			 * Se genera una nueva figura
			 */
			generarNuevaFigura();
		}		
	}
	
	/**
	 * Se pintan de nuevo los paneles
	 */
	private void renderGame() {
		tablero.repaint();
		panelGeneral.repaint();
	}
	
	/**
	 * Reinicia el juego
	 */
	private void reiniciarJuego() {
		this.nivel = 1;
		this.puntos = 0;
		this.velocidadJuego = 1.0f;
		this.siguienteFigura = Figura.values()[random.nextInt(TOTAL_FIGURAS)];
		this.esJuegoNuevo = false;
		this.esJuegoPerdido = false;		
		tablero.limpiar();
		relojLogico.reiniciarReloj();
		relojLogico.setCilosPorSegundo(velocidadJuego);
		generarNuevaFigura();
	}
		
	/**
	 * Coloca una nueva figura en el juego
	 */
	private void generarNuevaFigura() {
		/*
		 * Colocar valores por defecto para la nueva figura
		 */
		this.figuraActual = siguienteFigura;
		this.columnaActual = figuraActual.getColumnaInicial();
		this.filaActual = figuraActual.getFilaInicial();
		this.rotacionActual = 0;
		this.siguienteFigura = Figura.values()[random.nextInt(TOTAL_FIGURAS)];
		
		/*
		 * Si la posicion no es valida el juego se perdio
		 */
		if(!tablero.esPosicionValida(figuraActual, columnaActual, filaActual, rotacionActual)) {
			this.esJuegoPerdido = true;
			relojLogico.setPausado(true);
		}		
	}

	/**
	 * Le da rotacion a la figura
	 */
	private void rotarFigura(int newRotation) {
		
		int nuevaColumna = columnaActual;
		int nuevaFila = filaActual;
		
		/*
		 * Obtener las inserciones para cada uno de los lados. Estos se utilizan para determinar 
		 * el número de filas o columnas vacías que hay en un lado dado.
		 */
		int left = figuraActual.obtenerInsercionEnIzquierda(newRotation);
		int right = figuraActual.obtenerInsercionEnDerecha(newRotation);
		int top = figuraActual.obtenerInsercionSuperior(newRotation);
		int bottom = figuraActual.obtenerInsercionInferior(newRotation);
		
		/*
		 * alejar la pieza de los bordes
		 */
		if(columnaActual < -left) {
			nuevaColumna -= columnaActual - left;
		} else if(columnaActual + figuraActual.getDimension() - right >= Tablero.TOTAL_COLUMNAS) {
			nuevaColumna -= (columnaActual + figuraActual.getDimension() - right) - Tablero.TOTAL_COLUMNAS + 1;
		}
		
		if(filaActual < -top) {
			nuevaFila -= filaActual - top;
		} else if(filaActual + figuraActual.getDimension() - bottom >= Tablero.TOTAL_FILAS) {
			nuevaFila -= (filaActual + figuraActual.getDimension() - bottom) - Tablero.TOTAL_FILAS + 1;
		}
		
		/*
		 * Verifica si la nueva posicion es actptable y la cambia
		 */
		if(tablero.esPosicionValida(figuraActual, nuevaColumna, nuevaFila, newRotation)) {
			rotacionActual = newRotation;
			filaActual = nuevaFila;
			columnaActual = nuevaColumna;
		}
	}
	
	/**
	 * Indica si el juego esta pausado
	 * @return Si el juego esta pausado
	 */
	public boolean estaPausado() {
		return estaPausado;
	}
	
	/**
	 * Indica si el juego se ha perdido
	 * @return Si el juega se ha perdido
	 */
	public boolean esJuegoPerdido() {
		return esJuegoPerdido;
	}
	
	/**
	 * Indica si es un nuevo juego
	 * @return Si es un nuevo juego o no
	 */
	public boolean esNuevoJuego() {
		return esJuegoNuevo;
	}
	
	/**
	 * Gets el actual puntaje
	 * @return El puntaje.
	 */
	public int getPuntos() {
		return puntos;
	}
	
	/**
	 * Gets el actual nivel
	 * @return El nivel.
	 */
	public int getNivel() {
		return nivel;
	}
	
	/**
	 * Gets la actual figura
	 * @return la actual figura.
	 */
	public Figura getActualFigura() {
		return figuraActual;
	}
	
	/**
	 * Gets La siguiente figura
	 * @return La siguiente figura.
	 */
	public Figura getSiguienteFigura() {
		return siguienteFigura;
	}
	
	/**
	 * Gets la columna de la actual figura.
	 * @return La columna.
	 */
	public int getColumnaActual() {
		return columnaActual;
	}
	
	/**
	 * Gets la fila de la actual figura
	 * @return La fila.
	 */
	public int getFilaActual() {
		return filaActual;
	}
	
	/**
	 * Gets La rotacion de la actual figura
	 * @return La rotacion.
	 */
	public int getRotacionActual() {
		return rotacionActual;
	}

	/**
	 * Procedimiento principal y entrada al juego.
	 */
	public static void main(String[] args) {
		Tetris tetris = new Tetris();
		tetris.comenzarJuego();
	}

}
